package utils;

import java.util.Comparator;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Vector;

import problem.Heuristic;
import problem.logic;
import agents.Agent;
import agents.Search;
import env.City;
import env.Move;
import env.World;

/***
 * this class have similar functionalty to Dijkstra but for the ai searches it
 * will build a search tree and will have a node class it will recive the world
 * and an expansion strategy, some weighted queue for example
 * 
 * @author lior-13
 * 
 */
public class AiSearch {
	public int getExpands() {
		return expands;
	}
	public class AiNodeComparator implements Comparator<AiNode>{
		@Override
		public int compare(AiNode o1, AiNode o2) {
			return o1.cost - o2.cost;
		}		
	}
	
	public class AiNode {
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			AiNode other = (AiNode) obj;
			if (!getOuterType().equals(other.getOuterType()))
				return false;
			if (copiedWorld == null) {
				if (other.copiedWorld != null)
					return false;
			} else if (!copiedWorld.equals(other.copiedWorld))
				return false;
			if (!c.nameEquals(other.c)) {
				return false;
			}
			return true;
		}

		public City c;
		public AiNode parent;
		public int cost;
		public int agentScore;
		public AiSearch search;
		public World copiedWorld;
		public Move move; // move leading to this node

		public String toString() {
			return move + " node cost: " + cost +" agentScore: "+agentScore;
		}

		public AiNode(AiSearch search, City c, AiNode parent, int cost,
				World world) {
			this.c = c;
			this.cost = cost;
			this.parent = parent;
			this.copiedWorld = world;
			this.search = search;
			this.agentScore = 0;
		}

		public void expand() {
			expands++;// counting expands
			if (c.getNeighbors().isEmpty())// not supposed to happen but in just in case
				return;
			
			Iterator<City> it = c.getNeighbors().iterator();
			while (it.hasNext()) {
				City next = (City) it.next();								
				
				World copyOfcopy1 = new World(this.copiedWorld);//always try to move without chems and escort
				AiNode aiNode1 = new AiNode(search, copyOfcopy1.getCityByName(next.getName()), this, 0, copyOfcopy1);// cost will be calculated by strategy
				aiNode1.move = new Move(search.agent,
						origWorld.getRoadBetweenCities(c, next),
						origWorld.getCityByName(c.getName()),
						origWorld.getCityByName(next.getName()), false, false);
				Move moveInCopyOfCopy1 = copyOfcopy1.moveAgents(aiNode1.move);
				aiNode1.agentScore = this.agentScore + logic.calcCost(moveInCopyOfCopy1);
				strategy.calcCost(this, aiNode1, moveInCopyOfCopy1);
				queue.add(aiNode1);
				
				if (copiedWorld.getCityByName(c.getName()).getChems()>0){//chem in city try to move it
					World copyOfcopy2 = new World(this.copiedWorld);
					AiNode aiNode2 = new AiNode(search, copyOfcopy2.getCityByName(next.getName()), this, 0, copyOfcopy2);
					aiNode2.move = new Move(search.agent,
							origWorld.getRoadBetweenCities(c, next),
							origWorld.getCityByName(c.getName()),
							origWorld.getCityByName(next.getName()), false, true);
					Move moveInCopyOfCopy2 = copyOfcopy2.moveAgents(aiNode2.move);
					aiNode2.agentScore = this.agentScore + logic.calcCost(moveInCopyOfCopy2);
					strategy.calcCost(this, aiNode2, moveInCopyOfCopy2);
					queue.add(aiNode2);
				}
				
				if (copiedWorld.getCityByName(c.getName()).getEscorts()>0){//escort in city try to move it
					World copyOfcopy3 = new World(this.copiedWorld);
					AiNode aiNode3 = new AiNode(search, copyOfcopy3.getCityByName(next.getName()), this, 0, copyOfcopy3);// cost
					aiNode3.move = new Move(search.agent,
							origWorld.getRoadBetweenCities(c, next),
							origWorld.getCityByName(c.getName()),
							origWorld.getCityByName(next.getName()), true, false);
					Move moveInCopyOfCopy3 = copyOfcopy3.moveAgents(aiNode3.move);
					aiNode3.agentScore = this.agentScore + logic.calcCost(moveInCopyOfCopy3);
					strategy.calcCost(this, aiNode3, moveInCopyOfCopy3);
					queue.add(aiNode3);
				}
				
				if (copiedWorld.getCityByName(c.getName()).getChems()>0 && copiedWorld.getCityByName(c.getName()).getEscorts()>0) {//chems and escort try to move them both
					World copyOfcopy4 = new World(this.copiedWorld);
					AiNode aiNode4 = new AiNode(search, copyOfcopy4.getCityByName(next.getName()), this, 0, copyOfcopy4);// cost
					aiNode4.move = new Move(search.agent,
							origWorld.getRoadBetweenCities(c, next),
							origWorld.getCityByName(c.getName()),
							origWorld.getCityByName(next.getName()), true, true);
					Move moveInCopyOfCopy4 = copyOfcopy4.moveAgents(aiNode4.move);
					aiNode4.agentScore = this.agentScore + logic.calcCost(moveInCopyOfCopy4);
					strategy.calcCost(this, aiNode4, moveInCopyOfCopy4);
					queue.add(aiNode4);
				}
			}
			this.copiedWorld = null;
		}

		public Move getMove() {
			AiNode it = this;
			if (parent == null)
				return null;

			while (it.parent.parent != null) {

				it = it.parent;
			}
			Move res = it.move;
			it.parent = null;
			return res;
		}

		private AiSearch getOuterType() {
			return AiSearch.this;
		}

	}

	private City copiedInit;
	private City copiedGoal;
	private PriorityQueue<AiNode> queue;
	private Strategy strategy;
	private int cutOff;
	private int expands;
	private World origWorld;
	private World copiedWorld;
	public Agent agent;

	public AiSearch(Search agent, World origWorld, World copiedWorld,
			City copiedInit, City copiedGoal, Strategy strategy, int cutOff) {
		this.copiedInit = copiedInit;
		this.copiedGoal = copiedGoal;		
		this.strategy = strategy;
		this.cutOff = cutOff;
		this.expands = 0;
		this.origWorld = origWorld;
		this.copiedWorld = copiedWorld;
		this.agent = agent;
		queue = new PriorityQueueNoDupes<AiNode>(1, new AiNodeComparator());
		
	}

	public AiNode search() {
		queue.add(new AiNode(this, copiedInit, null, Heuristic.getHeuristic(
				this.copiedWorld, copiedGoal), this.copiedWorld));
		while (!queue.isEmpty()) {
			AiNode a = queue.remove();
			if (goalNode(a) || cutOff > 0 && expands == cutOff) {
				return a;
			}
			a.expand();
		}
		return null;
	}

	/**
	 * returns true if all chems are cleared
	 * 
	 * @param a
	 * @return
	 */
	private boolean goalNode(AiNode a) {
		boolean ans = a.c.nameEquals(copiedGoal)
				&& !a.copiedWorld.chemsOutsideCity(a.copiedWorld
						.getCityByName(a.c.getName())) && a.move.chems == true;

		return ans;
	}

}
